#include "glew/glew.h"

#include "SpriteBatch.hpp"
#include "Texture.hpp"
#include "Font.hpp"
#include "Color.hpp"

namespace fge
{

SpriteBatch::SpriteBatch(int maxSpritesPerDraw)
{
	currentTextureId = 0;

	glGenBuffers(1, &vboId);
	maxVertices = maxSpritesPerDraw * 4;
	activeVertices = 0;
	vertices = new Vertex[maxVertices];
}

SpriteBatch::~SpriteBatch()
{
	delete[] vertices;
}

void SpriteBatch::Begin(BlendMode blendMode, const fge::Matrix3 &batchTransform)
{
	this->blendMode = blendMode;
	this->batchTransform = batchTransform;
	currentTextureId = 0;
	activeVertices = 0;
}

void SpriteBatch::End()
{
	if(activeVertices < 4)
		return;

	if(activeVertices >= 0)
		DrawBuffer();

	currentTextureId = 0;
	activeVertices = 0;
}

void SpriteBatch::Draw(Texture *texture, const Vector2 &position, Rect *sourceRectangle,
		const Vector2 &origin, const float rotation, const Color &color, const Vector2 &scale, const Matrix3 &transformMatrix)
{
	if(activeVertices == maxVertices || (currentTextureId != 0 && currentTextureId != texture->GetTextureId()))
		DrawBuffer();

	currentTextureId = texture->GetTextureId();

	Rect textureRect(0, 0, texture->GetWidth(), texture->GetHeight());

	if(sourceRectangle == NULL)
		sourceRectangle = &textureRect;

	int width = sourceRectangle->w;
	int height = sourceRectangle->h;

	int textureWidth = texture->GetWidth();
	int textureHeight = texture->GetHeight();

	Vector3 v;
	Matrix3 transform = batchTransform * transformMatrix * CreateTranslationMatrix(position.X(), position.Y()) *
		CreateRotationMatrix(rotation) * CreateScaleMatrix(scale.X(), scale.Y());

	v.X() = -width * origin.X();
	v.Y() = -height * origin.Y();
	v.Z() = 1;
	v = transform * v;

	vertices[activeVertices].x = v.X();
	vertices[activeVertices].y = v.Y();
	vertices[activeVertices].r = color.R();
	vertices[activeVertices].g = color.G();
	vertices[activeVertices].b = color.B();
	vertices[activeVertices].a = color.A();
	vertices[activeVertices].u = (float)sourceRectangle->x / textureWidth;
	vertices[activeVertices++].v = (float)sourceRectangle->y / textureHeight;

	v.X() = width * (1 - origin.X());
	v.Y() = -height * origin.Y();
	v = transform * v;

	vertices[activeVertices].x = v.X();
	vertices[activeVertices].y = v.Y();
	vertices[activeVertices].r = color.R();
	vertices[activeVertices].g = color.G();
	vertices[activeVertices].b = color.B();
	vertices[activeVertices].a = color.A();
	vertices[activeVertices].u = (float)(sourceRectangle->x + sourceRectangle->w) / textureWidth;
	vertices[activeVertices++].v = (float)sourceRectangle->y / textureHeight;

	v.X() = width * (1 - origin.X());
	v.Y() = height * (1 - origin.Y());
	v = transform * v;

	vertices[activeVertices].x = v.X();
	vertices[activeVertices].y = v.Y();
	vertices[activeVertices].r = color.R();
	vertices[activeVertices].g = color.G();
	vertices[activeVertices].b = color.B();
	vertices[activeVertices].a = color.A();
	vertices[activeVertices].u = (float)(sourceRectangle->x + sourceRectangle->w) / textureWidth;
	vertices[activeVertices++].v = (float)(sourceRectangle->y + sourceRectangle->h) / textureHeight;

	v.X() = -width * origin.X();
	v.Y() = height * (1 - origin.Y());
	v = transform * v;

	vertices[activeVertices].x = v.X();
	vertices[activeVertices].y = v.Y();
	vertices[activeVertices].r = color.R();
	vertices[activeVertices].g = color.G();
	vertices[activeVertices].b = color.B();
	vertices[activeVertices].a = color.A();
	vertices[activeVertices].u = (float)sourceRectangle->x / textureWidth;
	vertices[activeVertices++].v = (float)(sourceRectangle->y + sourceRectangle->h) / textureHeight;
}

void SpriteBatch::DrawString(Font *font, const std::string &s, const Vector2 &position, const Color &color,
	const float rotation, const Vector2 &scale, const Matrix3 &transformMatrix)
{
	if(activeVertices == maxVertices || (currentTextureId != 0 && currentTextureId != font->GetTextureId()))
		DrawBuffer();

	currentTextureId = font->GetTextureId();

	float x = 0;
	float y = 0;

	for(size_t i=0; i<s.size(); i++)
	{
		if(activeVertices == maxVertices)
			DrawBuffer();

		if(s[i] == '\n' || s[i] == '\r')
		{
			x = 0;
			y += font->GetHeight();
			continue;
		}

		CharacterData &cd = font->GetCharacterData(static_cast<uint16_t>(s[i]));

		if(cd.width > 0 && cd.height > 0)
		{
			Matrix3 transform = batchTransform * transformMatrix * CreateTranslationMatrix(position.X(), position.Y()) *
				CreateRotationMatrix(rotation) * CreateScaleMatrix(scale.X(), scale.Y());

			Vector3 xy;
			Vector2 uv;

			xy = Vector3(x + cd.offsetX, y - cd.offsetY, 1);
			xy = transform * xy;
			uv = Vector2(cd.u1, cd.v1);
			vertices[activeVertices].x = xy.X();
			vertices[activeVertices].y = xy.Y();
			vertices[activeVertices].r = color.R();
			vertices[activeVertices].g = color.G();
			vertices[activeVertices].b = color.B();
			vertices[activeVertices].a = color.A();
			vertices[activeVertices].u = uv.X();
			vertices[activeVertices++].v = uv.Y();

			xy = Vector3(x + cd.offsetX + cd.width, y - cd.offsetY, 1);
			xy = transform * xy;
			uv = Vector2(cd.u2, cd.v1);
			vertices[activeVertices].x = xy.X();
			vertices[activeVertices].y = xy.Y();
			vertices[activeVertices].r = color.R();
			vertices[activeVertices].g = color.G();
			vertices[activeVertices].b = color.B();
			vertices[activeVertices].a = color.A();
			vertices[activeVertices].u = uv.X();
			vertices[activeVertices++].v = uv.Y();

			xy = Vector3(x + cd.offsetX + cd.width, y - cd.offsetY + cd.height, 1);
			xy = transform * xy;
			uv = Vector2(cd.u2, cd.v2);
			vertices[activeVertices].x = xy.X();
			vertices[activeVertices].y = xy.Y();
			vertices[activeVertices].r = color.R();
			vertices[activeVertices].g = color.G();
			vertices[activeVertices].b = color.B();
			vertices[activeVertices].a = color.A();
			vertices[activeVertices].u = uv.X();
			vertices[activeVertices++].v = uv.Y();

			xy = Vector3(x + cd.offsetX, y - cd.offsetY + cd.height, 1);
			xy = transform * xy;
			uv = Vector2(cd.u1, cd.v2);
			vertices[activeVertices].x = xy.X();
			vertices[activeVertices].y = xy.Y();
			vertices[activeVertices].r = color.R();
			vertices[activeVertices].g = color.G();
			vertices[activeVertices].b = color.B();
			vertices[activeVertices].a = color.A();
			vertices[activeVertices].u = uv.X();
			vertices[activeVertices++].v = uv.Y();
		}
		x += cd.advanceX;
	}
}

void SpriteBatch::DrawBuffer()
{
	switch(blendMode)
	{
	case BM_None:
		glBlendFunc(GL_ONE, GL_ZERO);
		break;
	case BM_Alpha:
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		break;
	case BM_Additive:
		glBlendFunc(GL_ONE, GL_ONE);
		break;
	case BM_PremultipliedAlpha:
		glBlendFunc(GL_SRC_ALPHA, GL_ONE);
		break;
	}

	glBindTexture(GL_TEXTURE_2D, currentTextureId);

	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glEnableClientState(GL_COLOR_ARRAY);

	glBindBuffer(GL_ARRAY_BUFFER, vboId);
	glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_STATIC_DRAW);
	glBufferData(GL_ARRAY_BUFFER, activeVertices * sizeof(Vertex), vertices, GL_STATIC_DRAW);

	glVertexPointer(2, GL_FLOAT, sizeof(Vertex), NULL);
	glTexCoordPointer(2, GL_FLOAT, sizeof(Vertex), (const GLvoid*)(2 * sizeof(float)));
	glColorPointer(4, GL_FLOAT, sizeof(Vertex), (const GLvoid*)(4 * sizeof(float)));

	glDrawArrays(GL_QUADS, 0, activeVertices);

	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	glDisableClientState(GL_COLOR_ARRAY);

	activeVertices = 0;
}

}
